//! 内核内存分配器实现
//!
//! `NOTE!:`
//! Don't use bitflags!
//!
//! 不要使用 bitflags!
//!
//! bitflags 造成严重的性能下降

use core::{alloc::GlobalAlloc, ops::BitAnd};

use self::{
    error::Result,
    global::{free_pages_to_freelist, get_page_from_freelist},
    slab::{kfree, kmalloc},
};
use crate::boot::virt_to_page;

mod global;
mod page;
mod slab;
mod zone;

pub use global::{
    kalloc_init, nr_free_pages, nr_reserved_pages, nr_total_pages, zone_manage_pages,
};
pub use page::Page;
pub use slab::vaddr_name;
pub mod error;

/// 页申请 flags
#[allow(non_camel_case_types)]
#[derive(Clone, Copy)]
#[repr(usize)]
pub enum GfpFlags {
    DMA      = 0b0000001,
    NORMAL   = 0b0000010,
    ZERO     = 0b0000100,
    ZERO_NORMAL = GfpFlags::NORMAL as usize | GfpFlags::ZERO as usize,
    ZERO_DMA = GfpFlags::DMA as usize | GfpFlags::ZERO as usize,
}

impl BitAnd for GfpFlags {
    type Output = usize;
    fn bitand(self, rhs: Self) -> Self::Output {
        self as usize & rhs as usize
    }
}

impl GfpFlags {
    /// 从 dma 区域申请
    #[inline(always)]
    pub fn zone_dma(self) -> bool {
        self & GfpFlags::DMA != 0
    }

    /// 从 normal 区域申请
    #[inline(always)]
    pub fn zone_normal(self) -> bool {
        self & GfpFlags::NORMAL != 0
    }

    /// 申请的页清零
    #[inline(always)]
    pub fn clear(self) -> bool {
        self & GfpFlags::ZERO != 0
    }
}

/// 申请内存页
///
/// 注意:
///
/// 这里不返回申请到的 page 引用, 而是返回 page 对应的线性地址,
/// 因为 page 本身是静态存在的, 申请者没有必要去特意获取 page,
/// 如果真有必要, 可以通过 virt_to_page{_mut} 来获取对应的页引用
pub fn alloc_pages(flags: GfpFlags, order: usize) -> Result<usize> {
    get_page_from_freelist(flags, order)
}

/// 释放内存页
pub fn free_pages(vaddr: usize, order: usize) {
    free_pages_to_freelist(vaddr, order);
}

/// 申请一页内存页
#[inline]
pub fn alloc_page(flags: GfpFlags) -> Result<usize> {
    alloc_pages(flags, 0)
}

/// 释放一页内存页
#[inline]
pub fn free_page(vaddr: usize) {
    free_pages(vaddr, 0)
}

/// 释放页不使用 order
pub fn free_pages_norder(vaddr: usize) {
    let page = virt_to_page(vaddr);
    let order = if page.is_PageComp() { page.compound_or_head_order() } else { 0 };
    free_pages(vaddr, order);
}

struct GAlloc;

#[global_allocator]
static GLOBAL_ALLOC: GAlloc = GAlloc;

unsafe impl GlobalAlloc for GAlloc {
    unsafe fn alloc(&self, layout: core::alloc::Layout) -> *mut u8 {
        let mut size = layout.size();
        let align = layout.align();
        size = (size + align - 1) & !(align - 1);
        let vaddr = kmalloc(size, GfpFlags::NORMAL).unwrap();
        vaddr as *mut u8
    }

    unsafe fn dealloc(&self, ptr: *mut u8, _: core::alloc::Layout) {
        let vaddr = ptr as usize;
        kfree(vaddr);
    }
}
